/**
 * Part.java
 * @author: liuex
 * 2007-12-27 ����12:53:44
 *
 * note: 
 */
package lex.mtd.core;

import java.io.*;
import java.util.logging.Logger;

import org.lex.io.IO;

import lex.mtd.core.net.DataSource;
import lex.mtd.utils.Logs;

/**
 * �����̣߳���DataSource��ȡ��ݣ����ص�FilePart�С��ɹ���ɺ��ر��ļ���
 * 
 * @author Daniel Liu
 */
public class DownloadThread implements Runnable {
	// properties
	private static final int BUF_SIZE = 1024;
	// ======================================
	private boolean running = false;
	private long startTime = 0L;
	private long finishedTime = 0L;
	private long transferredBytes = 0L;
	private int bufsize = BUF_SIZE;
	// -----------------------------
	private DownloadThreadObserver observer = null;
	private ContentLengthVerifier verifier = null;
	private FileBlock file = null;
	private DataSource source = null;
	private static Logger log = Logs.get(DownloadThread.class);

	public DownloadThread(DownloadThreadObserver observer, ContentLengthVerifier verifier, FileBlock file, DataSource src) {
		this.observer = observer;
		this.verifier = verifier;
		this.file = file;
		this.source = src;
	}

	public void stop() {
		this.running = false;
	}

	public boolean isRunning() {
		return this.running;
	}

	public FileBlock getFileBlock() {
		return this.file;
	}

	public DataSource getDataSource() {
		return this.source;
	}

	public long getTransferredBytes() {
		return this.transferredBytes;
	}

	/**
	 * @return milliseconds
	 */
	public long getElapsedTime() {
		if (true == running)
			return System.currentTimeMillis() - this.startTime;
		else
			return this.finishedTime - this.startTime;
	}

	/**
	 * @return byte/second
	 */
	public long getAverageSpeed() {
		long time = this.getElapsedTime();
		if (0 == time)
			return -1L;
		return this.transferredBytes * 1000 / time;
	}

	/**
	 * @return seconds
	 */
	public long getRemainTime() {
		long speed = this.getAverageSpeed();
		if (speed <= 0)
			return -1L;
		return (file.getLength() - transferredBytes) / getAverageSpeed();
	}

	public void run() {
		this.running = true;
		log.fine(file.getFile().getName() + " start to transfer");
		startTime = System.currentTimeMillis();
		// start get offset
		long seek = file.getStartPosition() + file.getOffset();
		// verify content length
		if (false == this.verifyContentLength(seek)) {
			this.running = false;
			return;
		}
		// open input stream
		InputStream in = this.openInputStream();
		if (null == in)
			return;
		// open out
		OutputStream out = this.openOutputStream();
		if (null == out) {
			IO.closeQuietly(in);
			return;
		}
		// get prepared for download
		byte[] buf = new byte[bufsize];
		int bytesRead = 0;
		long left = file.getRemainLength();
		int bytesToRead = bufsize;
		this.transferredBytes = 0L;
		while (true == running) {
			try {
				if (left < bufsize)
					bytesToRead = (int) left;
				else
					bytesToRead = bufsize;
				bytesRead = in.read(buf, 0, bytesToRead);
				out.write(buf, 0, bytesRead);
				transferredBytes += bytesRead;
				this.file.offset = transferredBytes;
				// length���ܻ��������߳��б��ı�
				left = file.getLength() - transferredBytes;
				if (left <= 0) {
					running = false;
					this.finishedTime = System.currentTimeMillis();
					IO.closeQuietly(out);
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
				IO.closeQuietly(in);
				IO.closeQuietly(out);
				this.running = false;
				this.observer.threadError(this);
				return;
			}
		}
		IO.closeQuietly(in);
		IO.closeQuietly(out);
		this.running = false;
		this.observer.threadFinished(this);
	}

	private boolean verifyContentLength(long seek) {
		long len = 0L;
		try {
			len = source.getContentLength(seek);
			if (false == verifier.verifyContentLength(len)) {
				log.info(source.getURL() + ": Content length confic: expect=" + verifier.getContentLength() + ", actual=" + len);
				return false;
			} else
				return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

	private InputStream openInputStream() {
		InputStream in = null;
		try {
			in = source.openInputStream();
			return in;
		} catch (Exception e) {
			e.printStackTrace();
			IO.closeQuietly(in);
			this.running = false;
			this.observer.threadError(this);
			return null;
		}
	}

	private OutputStream openOutputStream() {
		OutputStream out = null;
		try {
			out = new FileOutputStream(file.getFile());
			return out;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			IO.closeQuietly(out);
			this.running = false;
			this.observer.threadError(this);
			return null;
		}
	}

	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append("file       : ").append(file.getFile().getName());
		buf.append("\nstart      : ").append(this.file.getStartPosition());
		buf.append("\nlength     : ").append(this.file.getLength());
		buf.append("\ntransferred: ").append(this.transferredBytes);
		buf.append('\n');
		return buf.toString();
	}
}
